{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Аргументы; RVO / NRVO / copy elision"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Calling convention"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Вопрос-наброс:__ Как компилятор должен сформировать вызов функции?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "int my_max(int a, int b)\n",
    "{\n",
    "    return a < b ? b : a;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Как передать параметры - через регистры (а если их не хватит?), через стек, через heap?\n",
    "- Как их расположить, слева направо или справа налево?\n",
    "- Кто ответственный за переключение регистра, отвечающего за адрес выполняемой инструкции?\n",
    "- Должна ли вызываемая функция восстановить состояние регистров как было до вызова?\n",
    "- ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Соглашение о вызове — спецификация вызова функций:\n",
    "* способы передачи параметров\n",
    "* способы передачи управления\n",
    "* способы передачи результата\n",
    "* способы возврата управления"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Примеры:__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=vs-2019"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* `cdecl` - для языка С для функций с переменным числом аргументов\n",
    "    * аргументы передаются через стек справа налево\n",
    "    * вызывающий чистит стэк (он знает число аргументов)\n",
    "    * функция обязана восстановить состояние регистров как было до вызова\n",
    "        * компилятору на каждую функцию нужно генерировать код по сохранению регистров (prolog) и восстановлению (epilog)\n",
    "* `stdcall`\n",
    "    * аргументы передаются через стек справа налево\n",
    "    * функция сама чистит стек\n",
    "* `fastcall`\n",
    "    * параметры передаются через регистры (если влезают в них) - самый быстрый способ передачи\n",
    "    * функция сама чистит стек\n",
    "    * соглашение используется для вызова процедур и функций, не экспортируемых из исполняемого модуля и не импортируемых извне\n",
    "        * _А ты не забыл добавить `static` для функции, которая не нужна наружу?_\n",
    "* `thiscall`\n",
    "    * соглашение для вызова методов класса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Замечание:__ К вопросу о том что даёт оптимизация inlining по части вызова функции:\n",
    "* убирает дополнительные расходы на вызов функции\n",
    "* даёт гарантии о состоянии регистров\n",
    "    * если происходит вызов функции - часть регистров может потерять информацию\n",
    "    * если вызова нет, компилятор имеет гарантии по значениям в регистрах и может их переиспользовать, уменьшив доступы в память\n",
    "    \n",
    "    ```c++\n",
    "    void f()\n",
    "    {\n",
    "        int x = std::rand();\n",
    "        std::cout << x; // x должен появиться в регистрах, чтобы быть использованным\n",
    "        \n",
    "        do_some_work();\n",
    "        \n",
    "        std::cout << x; // если нет гарантий на сохранение регистров,\n",
    "                        // возможно, придётся перечитывать x со стека заново\n",
    "    }\n",
    "    ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Закинуть на godbolt.org и показать как компилятор работает со стеком и регистрами в зависимости от уровня оптимизаций, обратить внимание на разницу с `int` и `double`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "static int sqr(int x)\n",
    "{\n",
    "    return x * x;\n",
    "}\n",
    "\n",
    "int f(int x, int y)\n",
    "{\n",
    "    return sqr(x) + sqr(y);\n",
    "}\n",
    "\n",
    "static double sqr(double x)\n",
    "{\n",
    "    return x * x;\n",
    "}\n",
    "\n",
    "double f(double x, double y)\n",
    "{\n",
    "    return sqr(x) + sqr(y);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Передача аргументов в функцию"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Объяснение на cppcoreguidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f15-prefer-simple-and-conventional-ways-of-passing-information)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Правила для обычных смертных (до С++11)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](param-passing-normal.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Правила для тех, кто умеет в move-семантику (после С++11)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](param-passing-advanced.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ещё есть perfect forwarding и universal references, к ним вернёмся в продолжении курса [[1](https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c)][[2](https://en.cppreference.com/w/cpp/utility/forward)]."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Упражнение:__ Как организовать аргументы и возвращаемые значения для функций?\n",
    "\n",
    "<br />\n",
    "<details>\n",
    "<summary>Посчитать сумму float-ов в std::vector.</summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "float sum(const std::vector<float>& v);\n",
    "```\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>По int n посчитать std::vector первых n чисел ряда Фибоначи</summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "std::vector<int> fib(int n);\n",
    "```\n",
    "\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>Создать std::unique_ptr<Cat> по имени std::string</summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "std::unique_ptr<Cat> create_cat(const std::string& name);\n",
    "std::unique_ptr<Cat> create_cat(std::string name);  // + std::move\n",
    "std::unique_ptr<Cat> create_cat(std::string&& name);  // + std::move\n",
    "```\n",
    "\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>Отсортировать std::vector<int></summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "void sort_inplace(std::vector<int>& v);\n",
    "```\n",
    "\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>Отсортировать std::array<int, 1024></summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "void sort_inplace(std::array<int, 1024>& v);\n",
    "```\n",
    "\n",
    "В чём разница с `std::vector`?\n",
    "\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>Найти максимальный элемент в std::vector<int></summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "const int* max_integer(const std::vector<int>& v);\n",
    "std::vector<int>::const_iterator max_integer(const std::vector<int>& v);\n",
    "```\n",
    "\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary>Найти площадь треугольника по формуле Герона по трём сторонам</summary>\n",
    "<p>\n",
    "\n",
    "```c++\n",
    "std::optional<double> get_triangle_area(double a, double b, double c);\n",
    "double get_triangle_area(double a, double b, double c); // + throw exception\n",
    "\n",
    "// optional usage:\n",
    "std::optional<double> maybeArea = get_triangle_area(3, 4, 5);\n",
    "if (maybeArea.has_value())\n",
    "    std::cout << \"area is: \" << maybeArea.value() << std::endl;\n",
    "\n",
    "// отступление: более корректным был бы такой интерфейс:\n",
    "// double get_area(const Triangle& triangle);\n",
    "//\n",
    "// в этом варианте, если объект типа Triangle существует,\n",
    "// значит он корректен, и внутри всех функций, работающих\n",
    "// с треугольником\n",
    "```\n",
    "\n",
    "</p>\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Возвращаемое значение, RVO/NRVO"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://www.fluentcpp.com/2016/11/28/return-value-optimizations/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим пример:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house() {\n",
    "    House house;\n",
    "    house.add_floor(make_floor());\n",
    "    house.add_walls(make_walls());\n",
    "    house.add_roof(make_roof());\n",
    "    return house;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House h = build_house();\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В общем случае нужно сделать копию из локальной переменной house в возвращаемое значение.\n",
    "\n",
    "Но компилятор _иногда может_ копию оптимизировать."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "RVO:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house() {\n",
    "    // можно сконструировать |House| прямо в месте,\n",
    "    // отведённом под результат без промежуточной копии\n",
    "    return House(make_floor(),\n",
    "                 make_walls(),\n",
    "                 make_roof());\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house(bool stone) {\n",
    "    if (stone) {\n",
    "        return House(make_stone_floor(),\n",
    "                     make_stone_walls(),\n",
    "                     make_stone_roof());\n",
    "    } else {\n",
    "        return House(make_brick_floor(),\n",
    "                     make_brick_walls(),\n",
    "                     make_brick_roof());\n",
    "    }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NRVO:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house() {\n",
    "    // можно сконструировать |house| прямо в месте,\n",
    "    // отведённом под результат, без промежуточной копии,\n",
    "    // изменения можно проводить прямо в объекте возврата\n",
    "    House house;\n",
    "    house.add_floor(make_floor());\n",
    "    house.add_walls(make_walls());\n",
    "    house.add_roof(make_roof());\n",
    "    return house;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house(bool stone) {\n",
    "    House house;\n",
    "    house.add_floor(stone ? make_stone_floor() : make_brick_floor());\n",
    "    house.add_walls(stone ? make_stone_walls() : make_brick_walls());\n",
    "    house.add_roof (stone ? make_stone_roof()  : make_brick_roof());\n",
    "    return house;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда оптимизации не работают:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House build_house(int id) {\n",
    "    House stone_house;\n",
    "    stone_house.add_floor(make_stone_floor());\n",
    "    stone_house.add_walls(make_stone_walls());\n",
    "    stone_house.add_roof (make_stone_roof());\n",
    "    \n",
    "    House brick_house;\n",
    "    brick_house.add_floor(make_brick_floor());\n",
    "    brick_house.add_walls(make_brick_walls());\n",
    "    brick_house.add_roof (make_brick_roof());\n",
    "   \n",
    "    // компилятор не знает из какого объекта\n",
    "    // будет возвращаться значение, поэтому должен\n",
    "    // построить оба и скопировать нужный в результат.\n",
    "    return is_stone_house(id) ? stone_house : brick_house;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "House makeup_house(House house)\n",
    "{\n",
    "    house.cleanup_floor();\n",
    "    house.change_roof();\n",
    "    \n",
    "    // объект |house| уже сконструирован,\n",
    "    // придётся его либо скопировать в результат\n",
    "    // либо сделать move\n",
    "    return house;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Рекомендации до С++17**:\n",
    "* либо всегда конструировать объект в `return`\n",
    "\n",
    "    ```c++\n",
    "    House build() {\n",
    "        ...\n",
    "        return House(...);\n",
    "    }\n",
    "    ```\n",
    "\n",
    "* либо один объект для возврата в функции\n",
    "\n",
    "    ```c++\n",
    "    House build(bool stone) {\n",
    "        House house;\n",
    "        if (stone) {\n",
    "            ... // setup stone house\n",
    "        } else {\n",
    "            ... // setup another house\n",
    "        }        \n",
    "        return house;\n",
    "    }    \n",
    "    ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Copy elision"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Оптимизации RVO/NRVO получили развитие в [copy elision](https://en.cppreference.com/w/cpp/language/copy_elision) в С++17."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\"Copy elision does not elide copies\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чтобы избавиться от лишних копий / перемещений временных объектов стандартизаторы языка пошли дальше: вместо разрешения оптимизаций были докручены правила работы компилятора с prvalue - выражениями: prvalue можно назвать \"нематериализованным объектом\", и его конструирование откладывается до момента появления итогового объекта. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is \"unmaterialized value passing\": prvalues are returned and used without ever materializing a temporary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пример 1:\n",
    "    \n",
    "```c++\n",
    "T f() {\n",
    "    return T();\n",
    "}\n",
    "\n",
    "f();  // only one call to default constructor of T\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пример 2:\n",
    "    \n",
    "```c++\n",
    "T f() {\n",
    "    return T();\n",
    "}\n",
    "\n",
    "T x = T(T(f())); // only one call to default constructor of T, to initialize x\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Рекомендации после С++17:**\n",
    "\n",
    "* предпочтительнее конструировать объект в return, чтобы отработал copy elision, компилятор его гарантирует\n",
    "\n",
    "    ```c++\n",
    "    House build() {\n",
    "        ...\n",
    "        return House(...);\n",
    "    }\n",
    "    ```\n",
    "\n",
    "* если без именованного объекта не обойтись, желательно заиспользовать NRVO (и молиться на добрую волю компилятора)\n",
    "\n",
    "    ```c++\n",
    "    House build(bool stone) {\n",
    "        House house;\n",
    "        if (stone) {\n",
    "            ... // setup stone house\n",
    "        } else {\n",
    "            ... // setup another house\n",
    "        }\n",
    "        return house;\n",
    "    }\n",
    "    ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Для понимания__:\n",
    "* [CppCon 2018: Arthur O'Dwyer “Return Value Optimization: Harder Than It Looks”. Доклад про RVO и внутреннюю механику](https://www.youtube.com/watch?v=hA1WNtNyNbo)\n",
    "* [copy elision does not elide copies](https://devblogs.microsoft.com/cppblog/guaranteed-copy-elision-does-not-elide-copies/) "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
